/*==============================================================================
** Copyright (C) 2024-2027 WingSummer
**
** This program is free software: you can redistribute it and/or modify it under
** the terms of the GNU Affero General Public License as published by the Free
** Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
** FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
** details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <https://www.gnu.org/licenses/>.
** =============================================================================
*/

#ifndef SCRIPTQDICTIONARY_H
#define SCRIPTQDICTIONARY_H

// The dictionary class relies on the script string object, thus the script
// string type must be registered with the engine before registering the
// dictionary type

#ifndef ANGELSCRIPT_H
// Avoid having to inform include path if header is already include before
#include <angelscript.h>
#endif

// By default the CScriptDictionary use the std::string for the keys.
// If the application uses a custom string type, then this typedef
// can be changed accordingly.
#include <QHash>
#include <QString>
typedef QString dictKey_t;

// Forward declare CScriptDictValue so we can typedef the internal map type
BEGIN_AS_NAMESPACE
class CScriptDictValue;
END_AS_NAMESPACE

#include <unordered_map>
typedef std::unordered_map<dictKey_t, AS_NAMESPACE_QUALIFIER CScriptDictValue>
    dictMap_t;

#ifdef _MSC_VER
// Turn off annoying warnings about truncated symbol names
#pragma warning(disable : 4786)
#endif

BEGIN_AS_NAMESPACE

class CScriptArray;
class CScriptDictionary;

class CScriptDictValue {
public:
    // This class must not be declared as local variable in C++, because it
    // needs to receive the script engine pointer in all operations. The engine
    // pointer is not kept as member in order to keep the size down
    CScriptDictValue();
    CScriptDictValue(asIScriptEngine *engine, void *value, int typeId);

    // Destructor must not be called without first calling FreeValue, otherwise
    // a memory leak will occur
    ~CScriptDictValue();

    // Replace the stored value
    void Set(asIScriptEngine *engine, void *value, int typeId);
    void Set(asIScriptEngine *engine, const asQWORD &value);
    void Set(asIScriptEngine *engine, const asINT64 &value);
    void Set(asIScriptEngine *engine, const double &value);
    void Set(asIScriptEngine *engine, CScriptDictValue &value);

    // Gets the stored value. Returns false if the value isn't compatible with
    // the informed typeId
    bool Get(asIScriptEngine *engine, void *value, int typeId) const;
    bool Get(asIScriptEngine *engine, asQWORD &value) const;
    bool Get(asIScriptEngine *engine, asINT64 &value) const;
    bool Get(asIScriptEngine *engine, double &value) const;

    // Returns the address of the stored value for inspection
    const void *GetAddressOfValue() const;

    // Returns the type id of the stored value
    int GetTypeId() const;

    // Free the stored value
    void FreeValue(asIScriptEngine *engine);

    // GC callback
    void EnumReferences(asIScriptEngine *engine);

protected:
    friend class CScriptDictionary;

    union {
        asINT64 m_valueInt;
        double m_valueFlt;
        void *m_valueObj;
    };
    int m_typeId;
};

class CScriptDictionary {
public:
    // Factory functions
    static CScriptDictionary *Create(asIScriptEngine *engine);

    // Called from the script to instantiate a dictionary from an initialization
    // list
    static CScriptDictionary *Create(asBYTE *buffer);

    // Reference counting
    void AddRef() const;
    void Release() const;

    // Reassign the dictionary
    CScriptDictionary &operator=(const CScriptDictionary &other);

    // Sets a key/value pair
    void Set(const dictKey_t &key, void *value, int typeId);
    void Set(const dictKey_t &key, const asQWORD &value);
    void Set(const dictKey_t &key, const asINT64 &value);
    void Set(const dictKey_t &key, const double &value);

    // Gets the stored value. Returns false if the value isn't compatible with
    // the informed typeId
    bool Get(const dictKey_t &key, void *value, int typeId) const;
    bool Get(const dictKey_t &key, asQWORD &value) const;
    bool Get(const dictKey_t &key, asINT64 &value) const;
    bool Get(const dictKey_t &key, double &value) const;

    // Index accessors. If the dictionary is not const it inserts the value if
    // it doesn't already exist If the dictionary is const then a script
    // exception is set if it doesn't exist and a null pointer is returned
    CScriptDictValue *operator[](const dictKey_t &key);
    const CScriptDictValue *operator[](const dictKey_t &key) const;

    // Returns the type id of the stored value, or negative if it doesn't exist
    int GetTypeId(const dictKey_t &key) const;

    // Returns true if the key is set
    bool Exists(const dictKey_t &key) const;

    // Returns true if there are no key/value pairs in the dictionary
    bool IsEmpty() const;

    // Returns the number of key/value pairs in the dictionary
    asUINT GetSize() const;

    // Deletes the key
    bool Delete(const dictKey_t &key);

    // Deletes all keys
    void DeleteAll();

    // Get an array of all keys
    CScriptArray *GetKeys() const;

    asIScriptEngine *GetEngine() const;

    // STL style iterator
    class CIterator {
    public:
        void operator++();    // Pre-increment
        void operator++(int); // Post-increment

        // This is needed to support C++11 range-for
        CIterator &operator*();

        bool operator==(const CIterator &other) const;
        bool operator!=(const CIterator &other) const;

        // Accessors
        const dictKey_t &GetKey() const;
        int GetTypeId() const;
        bool GetValue(asQWORD &value) const;
        bool GetValue(asINT64 &value) const;
        bool GetValue(double &value) const;
        bool GetValue(void *value, int typeId) const;
        const void *GetAddressOfValue() const;

    protected:
        friend class CScriptDictionary;

        CIterator();
        CIterator(const CScriptDictionary &dict, dictMap_t::const_iterator it);

        CIterator &operator=(const CIterator &) { return *this; } // Not used

        dictMap_t::const_iterator m_it;
        const CScriptDictionary &m_dict;
    };

    CIterator begin() const;
    CIterator end() const;
    CIterator find(const dictKey_t &key) const;

    // Iterator to support foreach in script
    class CScriptDictIter {
    public:
        // Reference counting
        void AddRef() const;
        void Release() const;

    protected:
        friend class CScriptDictionary;

        CIterator iter;
        mutable int refCount;
        asUINT iterGuard;

        CScriptDictIter(const CScriptDictionary *dict);
        ~CScriptDictIter();
    };

    CScriptDictIter *opForBegin() const;
    bool opForEnd(const CScriptDictIter &iter) const;
    CScriptDictIter *opForNext(CScriptDictIter &iter) const;
    const CScriptDictValue &opForValue0(const CScriptDictIter &iter) const;
    const dictKey_t &opForValue1(const CScriptDictIter &iter) const;

    // Garbage collections behaviours
    int GetRefCount();
    void SetGCFlag();
    bool GetGCFlag();
    void EnumReferences(asIScriptEngine *engine);
    void ReleaseAllReferences(asIScriptEngine *engine);

protected:
    // Since the dictionary uses the asAllocMem and asFreeMem functions to
    // allocate memory the constructors are made protected so that the
    // application cannot allocate it manually in a different way
    CScriptDictionary(asIScriptEngine *engine);
    CScriptDictionary(asBYTE *buffer);

    // We don't want anyone to call the destructor directly, it should be called
    // through the Release method
    virtual ~CScriptDictionary();

    // Cache the object types needed
    void Init(asIScriptEngine *engine);

    // Our properties
    asIScriptEngine *engine;
    mutable int refCount;
    mutable bool gcFlag;
    dictMap_t dict;
    asUINT iterGuard;
};

// This function will determine the configuration of the engine
// and use one of the two functions below to register the dictionary object
void RegisterScriptDictionary(asIScriptEngine *engine);

// Call this function to register the math functions
// using native calling conventions
void RegisterScriptDictionary_Native(asIScriptEngine *engine);

// Use this one instead if native calling conventions
// are not supported on the target platform
void RegisterScriptDictionary_Generic(asIScriptEngine *engine);

END_AS_NAMESPACE

#endif
